﻿using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Ttifa.Infrastructure.Extensions;
using Ttifa.Infrastructure.Utils;

namespace Ttifa.MongoDB
{
    public class MongoDBHelper<T> : IDisposable where T : BaseEntity
    {
        /// <summary>
        /// 排序字段
        /// </summary>
        public string SortPropertyName = "Id";
        /// <summary>
        /// 是否倒序
        /// </summary>
        public bool IsDescending = false;

        public IMongoDatabase _database;

        public IMongoCollection<T> _collection;

        public MongoDBHelper(string collectonName = null, string connectionKey = null)
        {
            var connectionString = ConfigManager.GetConnection(connectionKey ?? "mongodb");
            var conn = new MongoUrl(connectionString);
            var client = new MongoClient(conn);
            _database = client.GetDatabase(conn.DatabaseName);

            if (string.IsNullOrEmpty(collectonName))
                collectonName = typeof(T).Name;

            _collection = _database.GetCollection<T>(collectonName);
        }

        #region CRUD
        public T Get(string id)
        {
            return _collection.Find(o => o.Id == id).FirstOrDefault();
        }

        public T Get(Expression<Func<T, bool>> match)
        {
            return _collection.Find(match).FirstOrDefault();
        }

        public void Insert(T model)
        {
            _collection.InsertOne(model);
        }

        public void Insert(IEnumerable<T> list)
        {
            _collection.InsertMany(list);
        }

        public long Delete(string id)
        {
            var result = _collection.DeleteOne(Builders<T>.Filter.Where(o => o.Id == id));
            return result.DeletedCount;
        }

        public long Delete(Expression<Func<T, bool>> match)
        {
            var result = _collection.DeleteMany(match);
            return result.DeletedCount;
        }

        public bool Update(T model)
        {
            var result = _collection.ReplaceOne(Builders<T>.Filter.Eq(e => e.Id, model.Id), model);
            return result.ModifiedCount > 0;
        }

        /// <summary>
        /// 更新文档内容
        /// </summary>
        /// <param name="match">条件</param>
        /// <param name="updateProps">更新键值对</param>
        /// <returns></returns>
        public bool Update(Expression<Func<T, bool>> match, Dictionary<string, object> updateProps)
        {
            if (updateProps.Count <= 0) return false;
            var filter = Builders<T>.Filter.Where(match);
            var updateFilters = new List<UpdateDefinition<T>>();
            foreach (var prop in updateProps)
            {
                updateFilters.Add(Builders<T>.Update.Set(prop.Key, prop.Value));
            }

            var result = _collection.UpdateMany(filter, Builders<T>.Update.Combine(updateFilters));
            return result.ModifiedCount > 0;
        }
        #endregion

        #region FilterDefinition

        /// <summary>
        /// 返回可查询的记录源
        /// </summary>
        /// <param name="query">查询条件</param>
        /// <returns></returns>
        public virtual IFindFluent<T, T> GetQueryable(FilterDefinition<T> query)
        {
            return GetQueryable(query, this.SortPropertyName, this.IsDescending);
        }

        /// <summary>
        /// 根据条件表达式返回可查询的记录源
        /// </summary>
        /// <param name="query">查询条件</param>
        /// <param name="sortPropertyName">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序，否则为升序</param>
        /// <returns></returns>
        public virtual IFindFluent<T, T> GetQueryable(FilterDefinition<T> query, string sortPropertyName, bool isDescending = true)
        {
            IFindFluent<T, T> queryable = _collection.Find(query);

            var sort = this.IsDescending ? Builders<T>.Sort.Descending(sortPropertyName) : Builders<T>.Sort.Ascending(sortPropertyName);
            return queryable.Sort(sort);
        }

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
        /// </summary>
        /// <param name="query">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public virtual IList<T> Page(FilterDefinition<T> query, int pageIndex, int pageSize, out int pageCount, out int total)
        {
            pageIndex = pageIndex < 1 ? 1 : pageIndex;
            pageSize = pageSize < 0 ? 10 : pageSize;
            int excludedRows = (pageIndex - 1) * pageSize;
            var find = GetQueryable(query);
            total = (int)find.Count();
            pageCount = total % pageSize == 0 ? (total / pageSize) : (total / pageSize + 1);
            return find.Skip(excludedRows).Limit(pageSize).ToList();
        }

        #endregion

        #region IQueryable

        public IQueryable<T> AsQueryable()
        {
            return _collection.AsQueryable();
        }

        /// <summary>
        /// 根据条件表达式返回可查询的记录源
        /// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="orderByProperty">排序表达式</param>
        /// <param name="isDescending">如果为true则为降序，否则为升序</param>
        /// <returns></returns>
        public IQueryable<T> GetQueryable<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty = null, bool isDescending = true)
        {
            IQueryable<T> query = _collection.AsQueryable();

            if (match != null)
            {
                query = query.Where(match);
            }

            if (orderByProperty != null)
            {
                query = isDescending ? query.OrderByDescending(orderByProperty) : query.OrderBy(orderByProperty);
            }
            else
            {
                query = query.OrderBy(this.SortPropertyName, isDescending);
            }
            return query;
        }


        /// <summary>
        /// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public IList<T> Page(Expression<Func<T, bool>> match, int pageIndex, int pageSize, out int pageCount, out int total)
        {
            pageIndex = pageIndex < 1 ? 1 : pageIndex;
            pageSize = pageSize < 0 ? 10 : pageSize;
            int excludedRows = (pageIndex - 1) * pageSize;
            IQueryable<T> query = GetQueryable<bool>(match);
            total = query.Count();
            pageCount = total % pageSize == 0 ? (total / pageSize) : (total / pageSize + 1);
            return query.Skip(excludedRows).Take(pageSize).ToList();
        }

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <param name="info">分页实体</param>
        /// <returns>指定对象的集合</returns>
        public List<T> GetPage(IQueryable<T> source, int pageIndex, int pageSize, out int pageCount, out int total)
        {
            total = source.Count();
            pageCount = total % pageSize == 0 ? (total / pageSize) : (total / pageSize + 1);
            return source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
        }

        #endregion

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                    _database = null;
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~MongoConnFactory() {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器，则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}
